/*
 * Copyright (c) 2013 Etsy Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language governing permissions and limitations under the
 * License.
 */

package com.pobing.uilibs.component.staggeredgrid;

import java.util.ArrayList;
import java.util.Arrays;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.Scroller;
import android.widget.TextView;

import com.pobing.uilibs.feature.callback.TouchEventCallback;
import com.pobing.uilibs.feature.features.internal.pullrefresh.RefreshController;
import com.pobing.uilibs.R;
import com.pobing.uilibs.feature.features.PullToRefreshFeature.OnPullToRefreshListener;
import com.pobing.uilibs.feature.features.internal.pullrefresh.IViewEdgeJudge;

/**
 * A staggered grid view which supports multiple columns with rows of varying sizes.
 * <p/>
 * Builds multiple columns on top of {@link ExtendableListView}
 * <p/>
 * Partly inspired by - https://github.com/huewu/PinterestLikeAdapterView
 */
public class StaggeredGridView extends ExtendableListView implements OnPullToRefreshListener, OnScrollListener, IViewEdgeJudge {

    private static int                                DEFAULT_AUTOLOAD_THRESHOLD = 10;

    // ////////////////////下拉刷新添加代码
    private OnPullToRefreshListener                   mRefreshListener;

    private boolean                                   mIsOnRefresh               = false;

    private RefreshController mRefreshController;
    private RefreshController.OnPullDownRefreshCancle mRefreshCancle;

    private Scroller                                  mScroller;

    private int                                       mAutoLoadThreshold;

    // ////////////////////定制的UI
    protected View                                    mLoadingTipView;

    protected ImageView                               mEmptyImageView;

    protected TextView                                mEmptyTextView;

    protected View                                    mEmptyTipView;

    private String                                    mEmptyTextTipStr;

    private boolean                                   mIsLoadingTipCreated       = false;

    private boolean                                   mIsLoadingTipOpened        = true;

    protected TextView                                mLoadingTextView;

    protected ProgressBar                             mLoadingProgress;

    // //////////////////数据
    private IStaggeredListViewController              mListViewController;

    private OnScrollListener                          mScrollListener;
    // ///////////////////

    private static final String                       TAG                        = "StaggeredGridView";
    private static final boolean                      DBG                        = false;

    private static final int                          DEFAULT_COLUMNS_PORTRAIT   = 2;
    private static final int                          DEFAULT_COLUMNS_LANDSCAPE  = 3;

    private int                                       mColumnCount;
    private int                                       mItemMargin;
    private int                                       mColumnWidth;
    private boolean                                   mNeedSync;

    private int                                       mColumnCountPortrait       = DEFAULT_COLUMNS_PORTRAIT;
    private int                                       mColumnCountLandscape      = DEFAULT_COLUMNS_LANDSCAPE;

    /**
     * A key-value collection where the key is the position and the {@link GridItemRecord} with some info about that
     * position so we can maintain it's position - and reorg on orientation change.
     */
    private SparseArray<GridItemRecord>               mPositionData;
    private int                                       mGridPaddingLeft;
    private int                                       mGridPaddingRight;
    private int                                       mGridPaddingTop;
    private int                                       mGridPaddingBottom;

    /***
     * Our grid item state record with {@link Parcelable} implementation so we can persist them across the SGV
     * lifecycle.
     */
    static class GridItemRecord implements Parcelable {

        int     column;
        double  heightRatio;
        boolean isHeaderFooter;

        GridItemRecord(){
        }

        /**
         * Constructor called from {@link #CREATOR}
         */
        private GridItemRecord(Parcel in){
            column = in.readInt();
            heightRatio = in.readDouble();
            isHeaderFooter = in.readByte() == 1;
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            out.writeInt(column);
            out.writeDouble(heightRatio);
            out.writeByte((byte) (isHeaderFooter ? 1 : 0));
        }

        @Override
        public String toString() {
            return "GridItemRecord.ListSavedState{" + Integer.toHexString(System.identityHashCode(this)) + " column:"
                   + column + " heightRatio:" + heightRatio + " isHeaderFooter:" + isHeaderFooter + "}";
        }

        public static final Parcelable.Creator<GridItemRecord> CREATOR = new Parcelable.Creator<GridItemRecord>() {

                                                                           public GridItemRecord createFromParcel(Parcel in) {
                                                                               return new GridItemRecord(in);
                                                                           }

                                                                           public GridItemRecord[] newArray(int size) {
                                                                               return new GridItemRecord[size];
                                                                           }
                                                                       };
    }

    /**
     * The location of the top of each top item added in each column.
     */
    private int[]                         mColumnTops;

    /**
     * The location of the bottom of each bottom item added in each column.
     */
    private int[]                         mColumnBottoms;

    /**
     * The left location to put items for each column
     */
    private int[]                         mColumnLefts;

    /***
     * Tells us the distance we've offset from the top. Can be slightly off on orientation change - TESTING
     */
    private int                           mDistanceToTop;

    private ArrayList<TouchEventCallback> mTouchEventListeners = new ArrayList<TouchEventCallback>();

    public StaggeredGridView(final Context context){
        this(context, null);
    }

    public StaggeredGridView(final Context context, final AttributeSet attrs){
        this(context, attrs, 0);
    }

    public void setTouchEventListener(TouchEventCallback listener) {
        this.mTouchEventListeners.add(listener);
    }

    public StaggeredGridView(final Context context, final AttributeSet attrs, final int defStyle){
        super(context, attrs, defStyle);

        if (attrs != null) {
            // get the number of columns in portrait and landscape
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.StaggeredGridView, defStyle, 0);

            mColumnCount = typedArray.getInteger(R.styleable.StaggeredGridView_uik_column_count, 0);

            if (mColumnCount > 0) {
                mColumnCountPortrait = mColumnCount;
                mColumnCountLandscape = mColumnCount;
            } else {
                mColumnCountPortrait = typedArray.getInteger(R.styleable.StaggeredGridView_uik_column_count_portrait,
                                                             DEFAULT_COLUMNS_PORTRAIT);
                mColumnCountLandscape = typedArray.getInteger(R.styleable.StaggeredGridView_uik_column_count_landscape,
                                                              DEFAULT_COLUMNS_LANDSCAPE);
            }

            mItemMargin = typedArray.getDimensionPixelSize(R.styleable.StaggeredGridView_uik_item_margin, 0);
            mGridPaddingLeft = typedArray.getDimensionPixelSize(R.styleable.StaggeredGridView_uik_grid_paddingLeft, 0);
            mGridPaddingRight = typedArray.getDimensionPixelSize(R.styleable.StaggeredGridView_uik_grid_paddingRight, 0);
            mGridPaddingTop = typedArray.getDimensionPixelSize(R.styleable.StaggeredGridView_uik_grid_paddingTop, 0);
            mGridPaddingBottom = typedArray.getDimensionPixelSize(R.styleable.StaggeredGridView_uik_grid_paddingBottom,
                                                                  0);

            typedArray.recycle();
        }

        mColumnCount = 0; // determined onMeasure
        // Creating these empty arrays to avoid saving null states
        mColumnTops = new int[0];
        mColumnBottoms = new int[0];
        mColumnLefts = new int[0];
        mPositionData = new SparseArray<GridItemRecord>();

        init();
    }

    // ///////////////////定制UI
    private void init() {

        super.setOnScrollListener(this);

        mEmptyTipView = createEmptyTipView();
        if (mEmptyTipView != null) {
            addFooterView(mEmptyTipView, null, true);
        }

        mLoadingTipView = createLoadingTipView();
        if (mLoadingTipView != null) {
            addFooterView(mLoadingTipView, null, false);
        }

        setAutoLoadThreshold(DEFAULT_AUTOLOAD_THRESHOLD);
    }

    private void initRefreshController() {
        if (mScroller == null) mScroller = new Scroller(this.getContext(), new DecelerateInterpolator());
        if (mRefreshController == null) {
            mRefreshController = new RefreshController(this, getContext(), mScroller);
            mRefreshCancle = new RefreshController.OnPullDownRefreshCancle() {

                @Override
                public void onRefreshCancle() {
                    computeScroll();
                }
            };
            mRefreshController.setRefreshCancle(mRefreshCancle);
        }
    }

    public void enablePullDownToRefresh(boolean enable) {
        ImageView view = new ImageView(this.getContext());
        view.setImageResource(R.drawable.uilibs_list_logo);
        view.setScaleType(ImageView.ScaleType.CENTER_INSIDE);

        enablePullDownToRefresh(enable, R.drawable.uilibs_arrow, 0, view);
    }

    public void enablePullUpToRefresh(boolean enable) {
        enablePullUpToRefresh(enable, R.drawable.uilibs_arrow, 0, null);
    }

    public void enablePullDownToRefresh(boolean enable, int arrowDrawableId, int progressViewID, View custom) {

        if (enable) {
            initRefreshController();
            mRefreshController.enablePullDownRefresh(enable, arrowDrawableId, progressViewID, custom);
            mRefreshController.addHeaderView();
        } else if (mRefreshController != null) {
            mRefreshController.enablePullDownRefresh(enable, 0, 0, custom);
        }

    }

    public void enablePullUpToRefresh(boolean enable, int arrowDrawableId, int progressViewID, View custom) {

        if (enable) {
            initRefreshController();
            mRefreshController.enablePullUpRefresh(enable, arrowDrawableId, progressViewID, custom);
            mRefreshController.addFooterView();
        } else if (mRefreshController != null) {
            mRefreshController.enablePullUpRefresh(enable, 0, 0, null);
        }
    }

    public void setPullDownRefreshTips(String[] array) {
        if (mRefreshController == null) {
            return;
        }

        mRefreshController.setDownRefreshTips(array);
    }

    public void setPullUpRefreshTips(String[] array) {
        if (mRefreshController == null) {
            return;
        }
        mRefreshController.setUpRefreshTips(array);
    }

    /**
     * 此方法只针对下拉刷新功能 设置正在进行下拉刷新 用于显示headView
     */
    public void setIsRefreshing() {
        if (mRefreshController != null) {
            mRefreshController.setIsDownRefreshing();
        }
    }

    public void setOnRefreshListener(OnPullToRefreshListener refreshListener) {
        mRefreshListener = refreshListener;
        if (mRefreshController != null) {
            mRefreshController.setOnRefreshListener(this);
        }
    }

    public int getAutoLoadThreshold() {
        return mAutoLoadThreshold;
    }

    public void setAutoLoadThreshold(int mAutoLoadThreshold) {
        this.mAutoLoadThreshold = mAutoLoadThreshold;
    }

    /**
     * 默认tip 开关,必须在setAdapter或bindDataLogic后调用,默认为开 重载了onCreateTipView，该方法将无效
     * 
     * @param enable true 开启 false 关闭
     */
    public void enableLoadingTip(boolean enable) {
        if (!mIsLoadingTipCreated)// 是否重载了onCreateTipView方法
        {
            return;
        }
        if (mIsLoadingTipOpened != enable) {
            if (!enable) {
                if (null != mLoadingTipView) {
                    removeFooterView(mLoadingTipView);
                    mLoadingTipView = null;
                }
            }
            mIsLoadingTipOpened = enable;
        }
    }

    /**
     * 默认MyTip 开关,必须在setAdapter或bindDataLogic后调用,默认为开
     * 
     * @param enable true 开启 false 关闭
     */
    public void enableEmptyTip(boolean enable) {
        if (!enable) {
            if (null != mEmptyTipView) {
                removeFooterView(mEmptyTipView);
                mEmptyTipView = null;
            }
        }
    }

    /**
     * 设置默认提示的文字颜色，如果重载了onCreateTipView，该方法将调用无效
     * 
     * @param color 字体颜色值
     */
    public void setLoadingTipColor(int color) {
        // 没有创建默认的tip
        if (!mIsLoadingTipCreated || !mIsLoadingTipOpened) {
            return;
        }

        // 设置tip文字
        if (mLoadingTextView != null) {
            mLoadingTextView.setTextColor(color);
        }

    }

    /**
     * 设置提示文字的背景，如果重载了onCreateTipView，该方法将调用无效
     * 
     * @param resourceId 背景资源id
     */
    public void setLoadingTipBackGroundResource(int resourceId) {
        // 没有创建默认的tip
        if (!mIsLoadingTipCreated || !mIsLoadingTipOpened) {
            return;
        }

        // 设置tip文字
        if (mLoadingTextView != null) {
            mLoadingTextView.setBackgroundResource(resourceId);
        }
    }

    public void setItemMargin(int itemMargin) {
        this.mItemMargin = itemMargin;
    }

    public void setLoadingTipDrawable(Drawable d) {
        if (!mIsLoadingTipCreated || !mIsLoadingTipOpened) {
            return;
        }

        if (mLoadingProgress != null) {
            mLoadingProgress.setIndeterminateDrawable(d);
            mLoadingProgress.setVisibility(VISIBLE);
        }

    }

    @SuppressWarnings("deprecation")
    public void setEmptyDrawable(Drawable drawable) {
        if (null != mEmptyImageView) {
            mEmptyImageView.setBackgroundDrawable(drawable);
        }
    }

    public void setEmptyStr(String str) {
        this.mEmptyTextTipStr = str;
    }

    protected View createLoadingTipView() {
        // DisplayMetrics metrics =
        // getContext().getResources().getDisplayMetrics();
        LinearLayout line = new LinearLayout(getContext());
        line.setOrientation(LinearLayout.HORIZONTAL);
        AbsListView.LayoutParams params = new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
                                                                       LayoutParams.WRAP_CONTENT);
        line.setLayoutParams(params);
        line.setGravity(Gravity.CENTER);
        line.setBackgroundColor(Color.TRANSPARENT);

        mLoadingProgress = new ProgressBar(getContext());
        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
                                                                     LayoutParams.WRAP_CONTENT);
        lp.setMargins(0, 0, 15, 0);
        mLoadingProgress.setLayoutParams(lp);
        mLoadingProgress.setIndeterminate(true);
        mLoadingProgress.setVisibility(GONE);
        line.addView(mLoadingProgress);

        mLoadingTextView = new TextView(getContext());
        mLoadingTextView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
                                                                       LayoutParams.WRAP_CONTENT));
        mLoadingTextView.setTextColor(0xff9d9d9d);
        mLoadingTextView.setBackgroundColor(Color.TRANSPARENT);
        mLoadingTextView.setTextSize(14);
        mLoadingTextView.setText("努力加载中...");
        line.addView(mLoadingTextView);
        line.setVisibility(GONE);

        mIsLoadingTipCreated = true;
        return line;
    }

    protected View createEmptyTipView() {
        LinearLayout line = new LinearLayout(getContext());
        line.setOrientation(LinearLayout.VERTICAL);
        AbsListView.LayoutParams params = new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
                                                                       LayoutParams.WRAP_CONTENT);
        line.setLayoutParams(params);
        line.setGravity(Gravity.CENTER);
        line.setBackgroundColor(Color.TRANSPARENT);

        mEmptyImageView = new ImageView(getContext());
        mEmptyImageView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
                                                                      LayoutParams.WRAP_CONTENT));
        // myImageTip.setScaleType(ScaleType.FIT_CENTER);
        line.addView(mEmptyImageView);

        mEmptyTextView = new TextView(getContext());
        mEmptyTextView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
                                                                     LayoutParams.WRAP_CONTENT));
        mEmptyTextView.setTextColor(Color.GRAY);
        mEmptyTextView.setBackgroundColor(Color.TRANSPARENT);
        mEmptyTextView.setTextSize(14);
        line.addView(mEmptyTextView);

        line.setVisibility(GONE);
        return line;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean result = false;
        try {
            for (TouchEventCallback touchListener : mTouchEventListeners) {
                touchListener.beforeOnTouchEvent(event);
            }

            if (mRefreshController != null) {
                mRefreshController.onTouchEvent(event);
            }
            result = super.onTouchEvent(event);
            for (TouchEventCallback touchListener : mTouchEventListeners) {
                touchListener.afterOnTouchEvent(event);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        for (TouchEventCallback touchListener : mTouchEventListeners) {
            touchListener.beforeDispatchTouchEvent(event);
        }
        boolean result = super.dispatchTouchEvent(event);

        for (TouchEventCallback touchListener : mTouchEventListeners) {
            touchListener.afterDispatchTouchEvent(event);
        }
        return result;
    }

    // /////////////////

    // //////////////////////////////////////////////////////////////////////////////////////////
    // PROPERTIES
    //

    // Grid padding is applied to the list item rows but not the header and footer
    public int getRowPaddingLeft() {
        return getListPaddingLeft() + mGridPaddingLeft;
    }

    public int getRowPaddingRight() {
        return getListPaddingRight() + mGridPaddingRight;
    }

    public int getRowPaddingTop() {
        return getListPaddingTop() + mGridPaddingTop;
    }

    public int getRowPaddingBottom() {
        return getListPaddingBottom() + mGridPaddingBottom;
    }

    public void setGridPadding(int left, int top, int right, int bottom) {
        mGridPaddingLeft = left;
        mGridPaddingTop = top;
        mGridPaddingRight = right;
        mGridPaddingBottom = bottom;
    }

    public void setColumnCountPortrait(int columnCountPortrait) {
        mColumnCountPortrait = columnCountPortrait;
        onSizeChanged(getWidth(), getHeight());
        requestLayoutChildren();
    }

    public void setColumnCountLandscape(int columnCountLandscape) {
        mColumnCountLandscape = columnCountLandscape;
        onSizeChanged(getWidth(), getHeight());
        requestLayoutChildren();
    }

    public void setColumnCount(int columnCount) {
        mColumnCountPortrait = columnCount;
        mColumnCountLandscape = columnCount;
        // mColumnCount set onSizeChanged();
        onSizeChanged(getWidth(), getHeight());
        requestLayoutChildren();
    }

    // //////////////////////////////////////////////////////////////////////////////////////////
    // MEASUREMENT
    //

    @Override
    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        if (mColumnCount <= 0) {
            boolean isLandscape = getMeasuredWidth() > getMeasuredHeight();
            mColumnCount = isLandscape ? mColumnCountLandscape : mColumnCountPortrait;
        }

        // our column width is the width of the listview
        // minus it's padding
        // minus the total items margin
        // divided by the number of columns
        mColumnWidth = calculateColumnWidth(getMeasuredWidth());

        if (mColumnTops == null || mColumnTops.length != mColumnCount) {
            mColumnTops = new int[mColumnCount];
            initColumnTops();
        }
        if (mColumnBottoms == null || mColumnBottoms.length != mColumnCount) {
            mColumnBottoms = new int[mColumnCount];
            initColumnBottoms();
        }
        if (mColumnLefts == null || mColumnLefts.length != mColumnCount) {
            mColumnLefts = new int[mColumnCount];
            initColumnLefts();
        }
    }

    @Override
    protected void onMeasureChild(final View child, final LayoutParams layoutParams) {
        final int viewType = layoutParams.viewType;
        final int position = layoutParams.position;

        if (viewType == ITEM_VIEW_TYPE_HEADER_OR_FOOTER || viewType == ITEM_VIEW_TYPE_IGNORE) {
            // for headers and weird ignored views
            super.onMeasureChild(child, layoutParams);
        } else {
            if (DBG) Log.d(TAG, "onMeasureChild BEFORE position:" + position + " h:" + getMeasuredHeight());
            // measure it to the width of our column.
            int childWidthSpec = MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY);
            int childHeightSpec;
            if (layoutParams.height > 0) {
                childHeightSpec = MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY);
            } else {
                childHeightSpec = MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.UNSPECIFIED);
            }
            child.measure(childWidthSpec, childHeightSpec);
        }

        final int childHeight = getChildHeight(child);
        setPositionHeightRatio(position, childHeight);

        if (DBG) Log.d(TAG, "onMeasureChild AFTER position:" + position + " h:" + childHeight);
    }

    public int getColumnWidth() {
        return mColumnWidth;
    }

    public void resetToTop() {
        if (mColumnCount > 0) {

            if (mColumnTops == null) {
                mColumnTops = new int[mColumnCount];
            }
            if (mColumnBottoms == null) {
                mColumnBottoms = new int[mColumnCount];
            }
            initColumnTopsAndBottoms();

            mPositionData.clear();
            mNeedSync = false;
            mDistanceToTop = 0;
            setSelection(0);
        }
    }

    // //////////////////////////////////////////////////////////////////////////////////////////
    // POSITIONING
    //

    @Override
    protected void onChildCreated(final int position, final boolean flowDown) {
        super.onChildCreated(position, flowDown);
        if (!isHeaderOrFooter(position)) {
            // do we already have a column for this position?
            final int column = getChildColumn(position, flowDown);
            setPositionColumn(position, column);
            if (DBG) Log.d(TAG, "onChildCreated position:" + position + " is in column:" + column);
        } else {
            setPositionIsHeaderFooter(position);
        }
    }

    private void requestLayoutChildren() {
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View v = getChildAt(i);
            if (v != null) v.requestLayout();
        }
    }

    @Override
    protected void layoutChildren() {
        preLayoutChildren();
        super.layoutChildren();
    }

    private void preLayoutChildren() {
        // on a major re-layout reset for our next layout pass
        if (!mNeedSync) {
            Arrays.fill(mColumnBottoms, 0);
        } else {
            mNeedSync = false;
        }
        // copy the tops into the bottom
        // since we're going to redo a layout pass that will draw down from
        // the top
        System.arraycopy(mColumnTops, 0, mColumnBottoms, 0, mColumnCount);
    }

    // NOTE : Views will either be layout out via onLayoutChild
    // OR
    // Views will be offset if they are active but offscreen so that we can recycle!
    // Both onLayoutChild() and onOffsetChild are called after we measure our view
    // see ExtensibleListView.setupChild();

    @Override
    protected void onLayoutChild(final View child, final int position, final boolean flowDown, final int childrenLeft,
                                 final int childTop, final int childRight, final int childBottom) {
        if (isHeaderOrFooter(position)) {
            layoutGridHeaderFooter(child, position, flowDown, childrenLeft, childTop, childRight, childBottom);
        } else {
            layoutGridChild(child, position, flowDown, childrenLeft, childRight);
        }
    }

    private void layoutGridHeaderFooter(final View child, final int position, final boolean flowDown,
                                        final int childrenLeft, final int childTop, final int childRight,
                                        final int childBottom) {
        // offset the top and bottom of all our columns
        // if it's the footer we want it below the lowest child bottom
        int gridChildTop;
        int gridChildBottom;

        if (flowDown) {
            gridChildTop = getLowestPositionedBottom();
            gridChildBottom = gridChildTop + getChildHeight(child);
        } else {
            gridChildBottom = getHighestPositionedTop();
            gridChildTop = gridChildBottom - getChildHeight(child);
        }

        for (int i = 0; i < mColumnCount; i++) {
            updateColumnTopIfNeeded(i, gridChildTop);
            updateColumnBottomIfNeeded(i, gridChildBottom);
        }

        super.onLayoutChild(child, position, flowDown, childrenLeft, gridChildTop, childRight, gridChildBottom);
    }

    private void layoutGridChild(final View child, final int position, final boolean flowDown, final int childrenLeft,
                                 final int childRight) {
        // stash the bottom and the top if it's higher positioned
        int column = getPositionColumn(position);

        int gridChildTop;
        int gridChildBottom;

        int childTopMargin = getChildTopMargin(position);
        int childBottomMargin = getChildBottomMargin();
        int verticalMargins = childTopMargin + childBottomMargin;

        if (flowDown) {
            gridChildTop = mColumnBottoms[column]; // the next items top is the last items bottom
            gridChildBottom = gridChildTop + (getChildHeight(child) + verticalMargins);
        } else {
            gridChildBottom = mColumnTops[column]; // the bottom of the next column up is our top
            gridChildTop = gridChildBottom - (getChildHeight(child) + verticalMargins);
        }

        if (DBG) Log.d(TAG, "onLayoutChild position:" + position + " column:" + column + " gridChildTop:"
                            + gridChildTop + " gridChildBottom:" + gridChildBottom);

        // we also know the column of this view so let's stash it in the
        // view's layout params
        GridLayoutParams layoutParams = (GridLayoutParams) child.getLayoutParams();
        layoutParams.column = column;

        updateColumnBottomIfNeeded(column, gridChildBottom);
        updateColumnTopIfNeeded(column, gridChildTop);

        // subtract the margins before layout
        gridChildTop += childTopMargin;
        gridChildBottom -= childBottomMargin;

        child.layout(childrenLeft, gridChildTop, childRight, gridChildBottom);
    }

    @Override
    protected void onOffsetChild(final View child, final int position, final boolean flowDown, final int childrenLeft,
                                 final int childTop) {
        // if the child is recycled and is just offset
        // we still want to add its deets into our store
        if (isHeaderOrFooter(position)) {

            offsetGridHeaderFooter(child, position, flowDown, childrenLeft, childTop);
        } else {
            offsetGridChild(child, position, flowDown, childrenLeft, childTop);
        }
    }

    private void offsetGridHeaderFooter(final View child, final int position, final boolean flowDown,
                                        final int childrenLeft, final int childTop) {
        // offset the top and bottom of all our columns
        // if it's the footer we want it below the lowest child bottom
        int gridChildTop;
        int gridChildBottom;

        if (flowDown) {
            gridChildTop = getLowestPositionedBottom();
            gridChildBottom = gridChildTop + getChildHeight(child);
        } else {
            gridChildBottom = getHighestPositionedTop();
            gridChildTop = gridChildBottom - getChildHeight(child);
        }

        for (int i = 0; i < mColumnCount; i++) {
            updateColumnTopIfNeeded(i, gridChildTop);
            updateColumnBottomIfNeeded(i, gridChildBottom);
        }

        super.onOffsetChild(child, position, flowDown, childrenLeft, gridChildTop);
    }

    private void offsetGridChild(final View child, final int position, final boolean flowDown, final int childrenLeft,
                                 final int childTop) {
        // stash the bottom and the top if it's higher positioned
        int column = getPositionColumn(position);

        int gridChildTop;
        int gridChildBottom;

        int childTopMargin = getChildTopMargin(position);
        int childBottomMargin = getChildBottomMargin();
        int verticalMargins = childTopMargin + childBottomMargin;

        if (flowDown) {
            gridChildTop = mColumnBottoms[column]; // the next items top is the last items bottom
            gridChildBottom = gridChildTop + (getChildHeight(child) + verticalMargins);
        } else {
            gridChildBottom = mColumnTops[column]; // the bottom of the next column up is our top
            gridChildTop = gridChildBottom - (getChildHeight(child) + verticalMargins);
        }

        if (DBG) Log.d(TAG, "onOffsetChild position:" + position + " column:" + column + " childTop:" + childTop
                            + " gridChildTop:" + gridChildTop + " gridChildBottom:" + gridChildBottom);

        // we also know the column of this view so let's stash it in the
        // view's layout params
        GridLayoutParams layoutParams = (GridLayoutParams) child.getLayoutParams();
        layoutParams.column = column;

        updateColumnBottomIfNeeded(column, gridChildBottom);
        updateColumnTopIfNeeded(column, gridChildTop);

        super.onOffsetChild(child, position, flowDown, childrenLeft, gridChildTop + childTopMargin);
    }

    private int getChildHeight(final View child) {
        return child.getMeasuredHeight();
    }

    private int getChildTopMargin(final int position) {
        boolean isFirstRow = position < (getHeaderViewsCount() + mColumnCount);
        return isFirstRow ? mItemMargin : 0;
    }

    private int getChildBottomMargin() {
        return mItemMargin;
    }

    @Override
    protected LayoutParams generateChildLayoutParams(final View child) {
        GridLayoutParams layoutParams = null;

        final ViewGroup.LayoutParams childParams = child.getLayoutParams();
        if (childParams != null) {
            if (childParams instanceof GridLayoutParams) {
                layoutParams = (GridLayoutParams) childParams;
            } else {
                layoutParams = new GridLayoutParams(childParams);
            }
        }
        if (layoutParams == null) {
            layoutParams = new GridLayoutParams(mColumnWidth, ViewGroup.LayoutParams.WRAP_CONTENT);
        }

        return layoutParams;
    }

    private void updateColumnTopIfNeeded(int column, int childTop) {
        if (childTop < mColumnTops[column]) {
            mColumnTops[column] = childTop;
        }
    }

    private void updateColumnBottomIfNeeded(int column, int childBottom) {
        if (childBottom > mColumnBottoms[column]) {
            mColumnBottoms[column] = childBottom;
        }
    }

    @Override
    protected int getChildLeft(final int position) {
        if (isHeaderOrFooter(position)) {
            return super.getChildLeft(position);
        } else {
            final int column = getPositionColumn(position);
            return mColumnLefts[column];
        }
    }

    @Override
    protected int getChildTop(final int position) {
        if (isHeaderOrFooter(position)) {
            return super.getChildTop(position);
        } else {
            final int column = getPositionColumn(position);
            if (column == -1) {
                return getHighestPositionedBottom();
            }
            return mColumnBottoms[column];
        }
    }

    /**
     * Get the top for the next child down in our view (maybe a column across) so we can fill down.
     */
    @Override
    protected int getNextChildDownsTop(final int position) {
        if (isHeaderOrFooter(position)) {
            return super.getNextChildDownsTop(position);
        } else {
            return getHighestPositionedBottom();
        }
    }

    @Override
    protected int getChildBottom(final int position) {
        if (isHeaderOrFooter(position)) {
            return super.getChildBottom(position);
        } else {
            final int column = getPositionColumn(position);
            if (column == -1) {
                return getLowestPositionedTop();
            }
            return mColumnTops[column];
        }
    }

    /**
     * Get the bottom for the next child up in our view (maybe a column across) so we can fill up.
     */
    @Override
    protected int getNextChildUpsBottom(final int position) {
        if (isHeaderOrFooter(position)) {
            return super.getNextChildUpsBottom(position);
        } else {
            return getLowestPositionedTop();
        }
    }

    @Override
    protected int getLastChildBottom() {
        final int lastPosition = mFirstPosition + (getChildCount() - 1);
        if (isHeaderOrFooter(lastPosition)) {
            return super.getLastChildBottom();
        }
        return getHighestPositionedBottom();
    }

    @Override
    protected int getFirstChildTop() {
        if (isHeaderOrFooter(mFirstPosition)) {
            return super.getFirstChildTop();
        }
        return getLowestPositionedTop();
    }

    @Override
    protected int getHighestChildTop() {
        if (isHeaderOrFooter(mFirstPosition)) {
            return super.getHighestChildTop();
        }
        return getHighestPositionedTop();
    }

    @Override
    protected int getLowestChildBottom() {
        final int lastPosition = mFirstPosition + (getChildCount() - 1);
        if (isHeaderOrFooter(lastPosition)) {
            return super.getLowestChildBottom();
        }
        return getLowestPositionedBottom();
    }

    @Override
    protected void offsetChildrenTopAndBottom(final int offset) {
        super.offsetChildrenTopAndBottom(offset);
        offsetAllColumnsTopAndBottom(offset);
        offsetDistanceToTop(offset);
    }

    protected void offsetChildrenTopAndBottom(final int offset, final int column) {
        if (DBG) Log.d(TAG, "offsetChildrenTopAndBottom: " + offset + " column:" + column);
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View v = getChildAt(i);
            if (v != null && v.getLayoutParams() != null && v.getLayoutParams() instanceof GridLayoutParams) {
                GridLayoutParams lp = (GridLayoutParams) v.getLayoutParams();
                if (lp.column == column) {
                    v.offsetTopAndBottom(offset);
                }
            }
        }
        offsetColumnTopAndBottom(offset, column);
    }

    private void offsetDistanceToTop(final int offset) {
        mDistanceToTop += offset;
        if (DBG) Log.d(TAG, "offset mDistanceToTop:" + mDistanceToTop);
    }

    public int getDistanceToTop() {
        return mDistanceToTop;
    }

    private void offsetAllColumnsTopAndBottom(final int offset) {
        if (offset != 0) {
            for (int i = 0; i < mColumnCount; i++) {
                offsetColumnTopAndBottom(offset, i);
            }
        }
    }

    private void offsetColumnTopAndBottom(final int offset, final int column) {
        if (offset != 0) {
            mColumnTops[column] += offset;
            mColumnBottoms[column] += offset;
        }
    }

    @Override
    protected void adjustViewsAfterFillGap(final boolean down) {
        super.adjustViewsAfterFillGap(down);
        // fix vertical gaps when hitting the top after a rotate
        // only when scrolling back up!
        if (!down) {
            alignTops();
        }
    }

    private void alignTops() {
        if (mFirstPosition == getHeaderViewsCount()) {
            // we're showing all the views before the header views
            int[] nonHeaderTops = getHighestNonHeaderTops();
            // we should now have our non header tops
            // align them
            boolean isAligned = true;
            int highestColumn = -1;
            int highestTop = Integer.MAX_VALUE;
            for (int i = 0; i < nonHeaderTops.length; i++) {
                // are they all aligned
                if (isAligned && i > 0 && nonHeaderTops[i] != highestTop) {
                    isAligned = false; // not all the tops are aligned
                }
                // what's the highest
                if (nonHeaderTops[i] < highestTop) {
                    highestTop = nonHeaderTops[i];
                    highestColumn = i;
                }
            }

            // skip the rest.
            if (isAligned) return;

            // we've got the highest column - lets align the others
            for (int i = 0; i < nonHeaderTops.length; i++) {
                if (i != highestColumn) {
                    // there's a gap in this column
                    int offset = highestTop - nonHeaderTops[i];
                    offsetChildrenTopAndBottom(offset, i);
                }
            }
            invalidate();
        }
    }

    private int[] getHighestNonHeaderTops() {
        int[] nonHeaderTops = new int[mColumnCount];
        int childCount = getChildCount();
        if (childCount > 0) {
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                if (child != null && child.getLayoutParams() != null
                    && child.getLayoutParams() instanceof GridLayoutParams) {
                    // is this child's top the highest non
                    GridLayoutParams lp = (GridLayoutParams) child.getLayoutParams();
                    // is it a child that isn't a header
                    if (lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER && child.getTop() < nonHeaderTops[lp.column]) {
                        nonHeaderTops[lp.column] = child.getTop();
                    }
                }
            }
        }
        return nonHeaderTops;
    }

    @Override
    protected void onChildrenDetached(final int start, final int count) {
        super.onChildrenDetached(start, count);
        // go through our remaining views and sync the top and bottom stash.

        // Repair the top and bottom column boundaries from the views we still have
        Arrays.fill(mColumnTops, Integer.MAX_VALUE);
        Arrays.fill(mColumnBottoms, 0);

        for (int i = 0; i < getChildCount(); i++) {
            final View child = getChildAt(i);
            if (child != null) {
                final LayoutParams childParams = (LayoutParams) child.getLayoutParams();
                if (childParams.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER && childParams instanceof GridLayoutParams) {
                    GridLayoutParams layoutParams = (GridLayoutParams) childParams;
                    int column = layoutParams.column;
                    int position = layoutParams.position;
                    final int childTop = child.getTop();
                    if (childTop < mColumnTops[column]) {
                        mColumnTops[column] = childTop - getChildTopMargin(position);
                    }
                    final int childBottom = child.getBottom();
                    if (childBottom > mColumnBottoms[column]) {
                        mColumnBottoms[column] = childBottom + getChildBottomMargin();
                    }
                } else {
                    // the header and footer here
                    final int childTop = child.getTop();
                    final int childBottom = child.getBottom();

                    for (int col = 0; col < mColumnCount; col++) {
                        if (childTop < mColumnTops[col]) {
                            mColumnTops[col] = childTop;
                        }
                        if (childBottom > mColumnBottoms[col]) {
                            mColumnBottoms[col] = childBottom;
                        }
                    }

                }
            }
        }
    }

    @Override
    protected boolean hasSpaceUp() {
        int end = mClipToPadding ? getRowPaddingTop() : 0;
        return getLowestPositionedTop() > end;
    }

    // //////////////////////////////////////////////////////////////////////////////////////////
    // SYNCING ACROSS ROTATION
    //

    @Override
    protected void onSizeChanged(final int w, final int h, final int oldw, final int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        onSizeChanged(w, h);
    }

    @Override
    protected void onSizeChanged(int w, int h) {
        super.onSizeChanged(w, h);
        boolean isLandscape = w > h;
        int newColumnCount = isLandscape ? mColumnCountLandscape : mColumnCountPortrait;
        if (mColumnCount != newColumnCount) {
            mColumnCount = newColumnCount;

            mColumnWidth = calculateColumnWidth(w);

            mColumnTops = new int[mColumnCount];
            mColumnBottoms = new int[mColumnCount];
            mColumnLefts = new int[mColumnCount];

            mDistanceToTop = 0;

            // rebuild the columns
            initColumnTopsAndBottoms();
            initColumnLefts();

            // if we have data
            if (getCount() > 0 && mPositionData.size() > 0) {
                onColumnSync();
            }

            requestLayout();
        }
    }

    private int calculateColumnWidth(final int gridWidth) {
        final int listPadding = getRowPaddingLeft() + getRowPaddingRight();
        return (gridWidth - listPadding - mItemMargin * (mColumnCount + 1)) / mColumnCount;
    }

    private int calculateColumnLeft(final int colIndex) {
        return getRowPaddingLeft() + mItemMargin + ((mItemMargin + mColumnWidth) * colIndex);
    }

    /***
     * Our mColumnTops and mColumnBottoms need to be re-built up to the mSyncPosition - the following layout request
     * will then layout the that position and then fillUp and fillDown appropriately.
     */
    private void onColumnSync() {
        // re-calc tops for new column count!
        int syncPosition = Math.min(mSyncPosition, getCount() - 1);

        SparseArray<Double> positionHeightRatios = new SparseArray<Double>(syncPosition);
        for (int pos = 0; pos < syncPosition; pos++) {
            // check for weirdness
            final GridItemRecord rec = mPositionData.get(pos);
            if (rec == null) break;

            Log.d(TAG, "onColumnSync:" + pos + " ratio:" + rec.heightRatio);
            positionHeightRatios.append(pos, rec.heightRatio);
        }

        mPositionData.clear();

        // re-calc our relative position while at the same time
        // rebuilding our GridItemRecord collection

        if (DBG) Log.d(TAG, "onColumnSync column width:" + mColumnWidth);

        for (int pos = 0; pos < syncPosition; pos++) {
            final GridItemRecord rec = getOrCreateRecord(pos);
            final double heightRatio = positionHeightRatios.get(pos);
            final int height = (int) (mColumnWidth * heightRatio);
            rec.heightRatio = heightRatio;

            int top;
            int bottom;
            // check for headers
            if (isHeaderOrFooter(pos)) {
                // the next top is the bottom for that column
                top = getLowestPositionedBottom();
                bottom = top + height;

                for (int i = 0; i < mColumnCount; i++) {
                    mColumnTops[i] = top;
                    mColumnBottoms[i] = bottom;
                }
            } else {
                // what's the next column down ?
                final int column = getHighestPositionedBottomColumn();
                // the next top is the bottom for that column
                top = mColumnBottoms[column];
                bottom = top + height + getChildTopMargin(pos) + getChildBottomMargin();

                mColumnTops[column] = top;
                mColumnBottoms[column] = bottom;

                rec.column = column;
            }

            if (DBG) Log.d(TAG, "onColumnSync position:" + pos + " top:" + top + " bottom:" + bottom + " height:"
                                + height + " heightRatio:" + heightRatio);
        }

        // our sync position will be displayed in this column
        final int syncColumn = getHighestPositionedBottomColumn();
        setPositionColumn(syncPosition, syncColumn);

        // we want to offset from height of the sync position
        // minus the offset
        int syncToBottom = mColumnBottoms[syncColumn];
        int offset = -syncToBottom + mSpecificTop;
        // offset all columns by
        offsetAllColumnsTopAndBottom(offset);

        // sync the distance to top
        mDistanceToTop = -syncToBottom;

        // stash our bottoms in our tops - though these will be copied back to the bottoms
        System.arraycopy(mColumnBottoms, 0, mColumnTops, 0, mColumnCount);
    }

    // //////////////////////////////////////////////////////////////////////////////////////////
    // GridItemRecord UTILS
    //

    private void setPositionColumn(final int position, final int column) {
        GridItemRecord rec = getOrCreateRecord(position);
        rec.column = column;
    }

    private void setPositionHeightRatio(final int position, final int height) {
        GridItemRecord rec = getOrCreateRecord(position);
        rec.heightRatio = (double) height / (double) mColumnWidth;
        if (DBG) Log.d(TAG, "position:" + position + " width:" + mColumnWidth + " height:" + height + " heightRatio:"
                            + rec.heightRatio);
    }

    private void setPositionIsHeaderFooter(final int position) {
        GridItemRecord rec = getOrCreateRecord(position);
        rec.isHeaderFooter = true;
    }

    private GridItemRecord getOrCreateRecord(final int position) {
        GridItemRecord rec = mPositionData.get(position, null);
        if (rec == null) {
            rec = new GridItemRecord();
            mPositionData.append(position, rec);
        }
        return rec;
    }

    private int getPositionColumn(final int position) {
        GridItemRecord rec = mPositionData.get(position, null);
        return rec != null ? rec.column : -1;
    }

    // //////////////////////////////////////////////////////////////////////////////////////////
    // HELPERS
    //

    private boolean isHeaderOrFooter(final int position) {
        final int viewType = mAdapter.getItemViewType(position);
        return viewType == ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
    }

    private int getChildColumn(final int position, final boolean flowDown) {

        // do we already have a column for this child position?
        int column = getPositionColumn(position);
        // we don't have the column or it no longer fits in our grid
        final int columnCount = mColumnCount;
        if (column < 0 || column >= columnCount) {
            // if we're going down -
            // get the highest positioned (lowest value)
            // column bottom
            if (flowDown) {
                column = getHighestPositionedBottomColumn();
            } else {
                column = getLowestPositionedTopColumn();

            }
        }
        return column;
    }

    private void initColumnTopsAndBottoms() {
        initColumnTops();
        initColumnBottoms();
    }

    private void initColumnTops() {
        Arrays.fill(mColumnTops, getPaddingTop() + mGridPaddingTop);
    }

    private void initColumnBottoms() {
        Arrays.fill(mColumnBottoms, getPaddingTop() + mGridPaddingTop);
    }

    private void initColumnLefts() {
        for (int i = 0; i < mColumnCount; i++) {
            mColumnLefts[i] = calculateColumnLeft(i);
        }
    }

    // //////////////////////////////////////////////////////////////////////////////////////////
    // BOTTOM
    //

    private int getHighestPositionedBottom() {
        final int column = getHighestPositionedBottomColumn();
        return mColumnBottoms[column];
    }

    private int getHighestPositionedBottomColumn() {
        int columnFound = 0;
        int highestPositionedBottom = Integer.MAX_VALUE;
        // the highest positioned bottom is the one with the lowest value :D
        for (int i = 0; i < mColumnCount; i++) {
            int bottom = mColumnBottoms[i];
            if (bottom < highestPositionedBottom) {
                highestPositionedBottom = bottom;
                columnFound = i;
            }
        }
        return columnFound;
    }

    private int getLowestPositionedBottom() {
        final int column = getLowestPositionedBottomColumn();
        return mColumnBottoms[column];
    }

    private int getLowestPositionedBottomColumn() {
        int columnFound = 0;
        int lowestPositionedBottom = Integer.MIN_VALUE;
        // the lowest positioned bottom is the one with the highest value :D
        for (int i = 0; i < mColumnCount; i++) {
            int bottom = mColumnBottoms[i];
            if (bottom > lowestPositionedBottom) {
                lowestPositionedBottom = bottom;
                columnFound = i;
            }
        }
        return columnFound;
    }

    // //////////////////////////////////////////////////////////////////////////////////////////
    // TOP
    //

    private int getLowestPositionedTop() {
        final int column = getLowestPositionedTopColumn();
        return mColumnTops[column];
    }

    private int getLowestPositionedTopColumn() {
        int columnFound = 0;
        // we'll go backwards through since the right most
        // will likely be the lowest positioned Top
        int lowestPositionedTop = Integer.MIN_VALUE;
        // the lowest positioned top is the one with the highest value :D
        for (int i = 0; i < mColumnCount; i++) {
            int top = mColumnTops[i];
            if (top > lowestPositionedTop) {
                lowestPositionedTop = top;
                columnFound = i;
            }
        }
        return columnFound;
    }

    private int getHighestPositionedTop() {
        final int column = getHighestPositionedTopColumn();
        return mColumnTops[column];
    }

    private int getHighestPositionedTopColumn() {
        int columnFound = 0;
        int highestPositionedTop = Integer.MAX_VALUE;
        // the highest positioned top is the one with the lowest value :D
        for (int i = 0; i < mColumnCount; i++) {
            int top = mColumnTops[i];
            if (top < highestPositionedTop) {
                highestPositionedTop = top;
                columnFound = i;
            }
        }
        return columnFound;
    }

    // //////////////////////////////////////////////////////////////////////////////////////////
    // LAYOUT PARAMS
    //

    /**
     * Extended LayoutParams to column position and anything else we may been for the grid
     */
    public static class GridLayoutParams extends LayoutParams {

        // The column the view is displayed in
        int column;

        public GridLayoutParams(Context c, AttributeSet attrs){
            super(c, attrs);
            enforceStaggeredLayout();
        }

        public GridLayoutParams(int w, int h){
            super(w, h);
            enforceStaggeredLayout();
        }

        public GridLayoutParams(int w, int h, int viewType){
            super(w, h);
            enforceStaggeredLayout();
        }

        public GridLayoutParams(ViewGroup.LayoutParams source){
            super(source);
            enforceStaggeredLayout();
        }

        /**
         * Here we're making sure that all grid view items are width MATCH_PARENT and height WRAP_CONTENT. That's what
         * this grid is designed for
         */
        private void enforceStaggeredLayout() {
            if (width != MATCH_PARENT) {
                width = MATCH_PARENT;
            }
            if (height == MATCH_PARENT) {
                height = WRAP_CONTENT;
            }
        }
    }

    // //////////////////////////////////////////////////////////////////////////////////////////
    // SAVED STATE

    public static class GridListSavedState extends ListSavedState {

        int         columnCount;
        int[]       columnTops;
        @SuppressWarnings("rawtypes")
        SparseArray positionData;

        public GridListSavedState(Parcelable superState){
            super(superState);
        }

        /**
         * Constructor called from {@link #CREATOR}
         */
        public GridListSavedState(Parcel in){
            super(in);
            columnCount = in.readInt();
            columnTops = new int[columnCount >= 0 ? columnCount : 0];
            in.readIntArray(columnTops);
            positionData = in.readSparseArray(GridItemRecord.class.getClassLoader());
        }

        @SuppressWarnings("unchecked")
        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeInt(columnCount);
            out.writeIntArray(columnTops);
            out.writeSparseArray(positionData);
        }

        @Override
        public String toString() {
            return "StaggeredGridView.GridListSavedState{" + Integer.toHexString(System.identityHashCode(this)) + "}";
        }

        public static final Creator<GridListSavedState> CREATOR = new Creator<GridListSavedState>() {

                                                                    public GridListSavedState createFromParcel(Parcel in) {
                                                                        return new GridListSavedState(in);
                                                                    }

                                                                    public GridListSavedState[] newArray(int size) {
                                                                        return new GridListSavedState[size];
                                                                    }
                                                                };
    }

    @Override
    public Parcelable onSaveInstanceState() {
        ListSavedState listState = (ListSavedState) super.onSaveInstanceState();
        GridListSavedState ss = new GridListSavedState(listState.getSuperState());

        // from the list state
        ss.selectedId = listState.selectedId;
        ss.firstId = listState.firstId;
        ss.viewTop = listState.viewTop;
        ss.position = listState.position;
        ss.height = listState.height;

        // our state

        boolean haveChildren = getChildCount() > 0 && getCount() > 0;

        if (haveChildren && mFirstPosition > 0) {
            ss.columnCount = mColumnCount;
            ss.columnTops = mColumnTops;
            ss.positionData = mPositionData;
        } else {
            ss.columnCount = mColumnCount >= 0 ? mColumnCount : 0;
            ss.columnTops = new int[ss.columnCount];
            ss.positionData = new SparseArray<Object>();
        }

        return ss;
    }

    @SuppressWarnings("unchecked")
    @Override
    public void onRestoreInstanceState(Parcelable state) {
        GridListSavedState ss = (GridListSavedState) state;
        mColumnCount = ss.columnCount;
        mColumnTops = ss.columnTops;
        mColumnBottoms = new int[mColumnCount];
        mPositionData = ss.positionData;
        mNeedSync = true;
        super.onRestoreInstanceState(ss);
    }

    /**
     * 开始下载数据通知
     */
    public void onStartReceive() {
        if (null != mLoadingTipView) {
            mLoadingTipView.setVisibility(VISIBLE);
        }

        if (null != mEmptyImageView) {
            mEmptyImageView.setVisibility(GONE);
        }

        if (null != mEmptyTextView) {
            mEmptyTextView.setVisibility(GONE);
        }

        if (null != mEmptyTipView) {
            mEmptyTipView.setVisibility(GONE);
        }
    }

    /**
     * 所有数据下载完成通知
     */
    public void onLoadFinish() {
        if (null != mLoadingTipView) {
            mLoadingTipView.setVisibility(GONE);
        }
    }

    /**
     * 出现错误通知
     */
    public void onError(String errorcode, String errString) {
        if (mIsOnRefresh) {
            mIsOnRefresh = false;
            onRefreshComplete();
        }
        if (null != mLoadingTipView) {
            mLoadingTipView.setVisibility(GONE);
        }

        if (null != mEmptyTipView) {
            mEmptyTipView.setVisibility(VISIBLE);
            if (null != mEmptyTextView) {
                mEmptyTextView.setVisibility(VISIBLE);
                mEmptyTextView.setText("加载失败");
            }

            if (null != mEmptyImageView) {
                mEmptyImageView.setVisibility(GONE);
            }
        }
    }

    /**
     * 一页完成错误通知
     */
    public void onDataReceived() {
        if (mIsOnRefresh) {
            mIsOnRefresh = false;
            onRefreshComplete();
        }
        if (null != mLoadingTipView) {
            mLoadingTipView.setVisibility(GONE);
        }

        if (mListViewController != null && mListViewController.getItemCount() <= 0) {
            if (null != mEmptyTipView) {
                mEmptyTipView.setVisibility(VISIBLE);
                if (null != mEmptyTextView) {
                    mEmptyTextView.setText(mEmptyTextTipStr);
                    mEmptyTextView.setVisibility(VISIBLE);
                }

                if (null != mEmptyImageView) {
                    mEmptyImageView.setVisibility(VISIBLE);
                }
            }
        } else {
            if (null != mEmptyImageView) {
                mEmptyImageView.setVisibility(GONE);
            }

            if (null != mEmptyTextView) {
                mEmptyTextView.setVisibility(GONE);
            }

            if (null != mEmptyTipView) {
                mEmptyTipView.setVisibility(GONE);
            }
        }
    }

    @Override
    public void onPullDownToRefresh() {
        mIsOnRefresh = true;
        if (mRefreshListener != null) {
            mRefreshListener.onPullDownToRefresh();
        }
    }

    @Override
    public void onPullUpToRefresh() {
        mIsOnRefresh = true;
        if (mRefreshListener != null) {
            mRefreshListener.onPullUpToRefresh();
        }
    }

    public void onRefreshComplete() {
        if (mRefreshController != null) {
            mRefreshController.onRefreshComplete();
        }
    }

    public IStaggeredListViewController getListViewController() {
        return this.mListViewController;
    }

    /**
     * 绑定controller
     * 
     * @param controller listController实例
     */
    @SuppressLint("NewApi")
    public void bindListViewController(IStaggeredListViewController controller) {
        this.mListViewController = controller;
        // dataLogic.setStateListener(this);
        setOnScrollListener(controller);
        setAdapter(controller.getAdapter());
    }

    @Override
    public void setOnScrollListener(OnScrollListener l) {
        mScrollListener = l;
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (mScrollListener != null) {
            mScrollListener.onScrollStateChanged(view, scrollState);
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

        if (mScrollListener != null) {
            mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
        }
    }

    @Override
    public void computeScroll() {
        if (mScroller != null && mScroller.computeScrollOffset()) {
            if (mRefreshController != null) {
                mRefreshController.onScrollerStateChanged(mScroller.getCurrY(), true);
            }
            postInvalidate();
        } else {
            if (mRefreshController != null) {
                mRefreshController.onScrollerStateChanged(mScroller.getCurrY(), false);
            }
        }
        super.computeScroll();
    }

    @Override
    public boolean hasArrivedTopEdge() {
        return getFirstVisiblePosition() == 0;
    }

    @Override
    public boolean hasArrivedBottomEdge() {
        return getLastVisiblePosition() == getCount() - 1 && getFirstVisiblePosition() != 0;
    }

    @Override
    public void setHeadView(View view) {
        addHeaderView(view);
    }

    @Override
    public void setFooterView(View view) {
        addFooterView(view);
    }

    @Override
    public void keepTop() {
        // TODO Auto-generated method stub

    }

    @Override
    public void keepBottom() {
        // TODO Auto-generated method stub

    }
}
